package fda.base;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.LineNumberReader;
import java.util.LinkedList;
import java.util.List;

import fda.schema.TableInfo;

/**
 * Class represent stored file in Disk. It has useful methods to load data file and read block-by-block.
 * The class may be also used to store sorted tuples from memory into file.
 * The class has to be used for any read/write blocks from the disk
 */
public class DiskFile {

	private final String absFilePath;
	private int tupleCount;
	private final boolean countWrites;
	private final TableInfo tableInfo;

	/**
	 * Constructor to create a File instance from existing data file (Text file)
	 * @param absPath Absolute Path to existing data file to read from
	 */
	public DiskFile(TableInfo info) {
		this.tableInfo = info;
		this.countWrites = true;
		absFilePath = info.getDataPath();
		tupleCount = countLines();
	}

	/**
	 * @return Information about table that the DiskFile is used
	 */
	public TableInfo getTableInfo() {
		return tableInfo;
	}

	/**
	 * Constructor to create a File instance from memory. All tuples from memory will be written to file system with <b>absPath</b>
	 * @param absPath Absolute Path to destination file where all tuples will be stored
	 * @param tuples Tuples to store in file <b>absPath</b>
	 */
	public DiskFile(String absPath, Tuple[] tuples, TableInfo table, boolean countIOwrites) {
		this.tableInfo = table;
		this.countWrites = countIOwrites;
		absFilePath = absPath;
		tupleCount = writeTuplesToFile(absFilePath, tuples, false);
	}

	/**
	 * @return Number of Tuples in current DiskFile
	 */
	public int getTupleCount() {
		return tupleCount;
	}

	/**
	 * @return Number of Blocks in current DiskFile
	 */
	public int getBlockCount() {
		int reminder = tupleCount % Constants.MAX_TYPLES_IN_BLOCK;
		int blocks = tupleCount / Constants.MAX_TYPLES_IN_BLOCK;
		if (reminder > 0)
			blocks++;

		return blocks;
	}

	/**
	 * @return Absolute Path of file where current DiskFile is physically stored
	 */
	public String getFilePath() {
		return absFilePath;
	}

	/**
	 * Read part of data file that corresponds requested block index
	 * @param index index of block to read from file
	 * @return list of tuples with size=40 (or less for last tuple in file)
	 */
	public List<Tuple> getBlock(int i) {
		return getBlock(i, true);
	}
	
	/**
	 * Read part of data file that corresponds requested block index
	 * @param index index of block to read from file
	 * @param count count all IO read operation if TRUE
	 * @return list of tuples with size=40 (or less for last tuple in file)
	 */
	public List<Tuple> getBlock(int index, boolean count) {
		if(count)
			IOCounter.getInst().incr_block();
		
		if (index < 0 || index >= getBlockCount())
			throw new IllegalArgumentException("There is no block #" + index + " in file: " + absFilePath);

		String textLine = null;
		List<Tuple> result = new LinkedList<Tuple>();
		File f = new File(absFilePath);
		try {
			LineNumberReader lnr = null;
			try {
				lnr = new LineNumberReader(new FileReader(f));
				
				// move to requested line
				while(lnr.getLineNumber() + 1 <= Constants.MAX_TYPLES_IN_BLOCK * index)
					lnr.readLine();
				do{
					textLine = lnr.readLine();
					if(textLine == null)
						break;
					result.add(new Tuple(textLine, tableInfo));
				} while (textLine != null && result.size() < Constants.MAX_TYPLES_IN_BLOCK);
			}
			finally {
				if (lnr != null)
					lnr.close();
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}

		return result;
	}

	/**
	 * Appends a block to existing blocks
	 * @param block Block of tuples to append to existing DiskFile
	 */
	public void appendBlock(List<Tuple> block) {
		int appended = writeTuplesToFile(absFilePath, block.toArray(new Tuple[0]), true);
		tupleCount += appended;
	}

	/**
	 * Appends tuples to existing blocks
	 * @param tuples to append to existing DiskFile
	 */
	public void appendTuples(Tuple[] tuples) {
		int appended = writeTuplesToFile(absFilePath, tuples, true);
		tupleCount += appended;
	}

	private int writeTuplesToFile(String absPath, Tuple[] tuples, boolean append) {
		File f = new File(absPath);
		int count = 0;
		// David: I commented out this, because it always gave me an exception when I wanted to run for debugging purposes
		//if (f.exists())
		//	throw new IllegalStateException("File " + absPath + " already exists!!! Data in file may be destroyed.");
		
		if(tuples != null) {
			try {
				FileWriter fw = null;
				try {
					fw = new FileWriter(f, append);
					for (Tuple t : tuples) {
						if(countWrites)
							IOCounter.getInst().incr_tuple();
						fw.write(t.getLine() + "\n");
						count++;
					}
				}
				finally {
					if (fw != null)
						fw.close();
				}
			}
			catch (Exception ex) {
				throw new IllegalStateException("Error when writing Tuples to file: " + ex.getMessage());
			}
		}

		return count;
	}

	private int countLines() {
		File f = new File(absFilePath);
		if (!f.exists())
			throw new IllegalStateException("Cannot find the file: " + absFilePath);

		try {
			LineNumberReader lnr = null;
			try {
				lnr = new LineNumberReader(new FileReader(f));
				lnr.skip(Long.MAX_VALUE);
				return lnr.getLineNumber();
			}
			finally {
				if (lnr != null)
					lnr.close();
			}
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		throw new IllegalStateException("Cannot count number of lines in file: " + absFilePath);
	}
	
	public static void clearAllFiles(String folder){
		File dir = new File(folder);
		File[] listOfFiles = dir.listFiles();
		for(File f : listOfFiles){
			f.delete();
		}
	}
	
	/**
	 * Clear all files in folder except that start with prefix: exceptPrefix
	 * @param folder folder to delete all files
	 * @param exceptPrefix prefix for files to skip (not delete)
	 */
	public static void clearFiles(String folder, String exceptPrefix){
		File dir = new File(folder);
		File[] listOfFiles = dir.listFiles();
		for(File f : listOfFiles){
			if(!f.getName().startsWith(exceptPrefix))
				f.delete();
		}
	}

}
